/*
 * Copyright (c) 2009 by ZDO Corporation.
 * Author: A. Onur Cinar
 * 
 * http://code.google.com/p/zdo-struts2
 * http://www.zdo.com
 * 
 * This file is part of ZDO Struts 2 Applications.
 *
 * ZDO-Struts2 is free software: you can redistribute it and/or
 * modify it under the terms of  the GNU General Public License
 * as published by the Free Software Foundation, either version
 *  3 of the License, or any later version.
 *
 * ZDO-Struts2 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY  or  FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 */

package com.zdo.security;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;

/**
 * Security interceptor. Intercepts actions and provides the following:
 * 
 * <ul>
 * <li>If action is {@link SecurityAware}, injects current 
 * {@link SecurityContext} instance.</li>
 * <li>If role is supplied, checks if user is in the required role.
 * Throws {@link NotLoggedInException} if user is not logged in.
 * Throws {@link NotInRoleException} if user is not in role.</li>
 * </ul>
 * 
 * Example:
 * 
 * <pre>
 * &lt;interceptor 
 *     name=&quot;security&quot; 
 *     class=&quot;com.zdo.security.SecurityInterceptor&quot; /&gt;
 *      
 *     &lt;interceptor-ref name=&quot;security&quot;&gt;
 *         &lt;param name=&quot;role&quot;&gt;<em>required role</em>&lt;/param&gt;
 *     &lt;/interceptor-ref&gt;
 *     
 * &lt;/interceptor&gt;
 * 
 * &lt;global-exception-mappings&gt;
 *   &lt;exception-mapping
 *       result=&quot;login!input&quot;
 *       exception=&quot;com.zdo.security.NotLoggedInException&quot; /&gt;
 *       
 *   &lt;exception-mapping
 *       result=&quot;access-denied&quot;
 *       exception=&quot;com.zdo.security.NotInRoleException&quot; /&gt;
 * &lt;/global-exception-mappings&gt;
 * </pre>
 */
public class SecurityInterceptor implements Interceptor
{
	private static final long serialVersionUID = -5187646452664082309L;
	
	private String role;

	/**
	 * Sets the required role.
	 * 
	 * Example:
	 * 
	 * <pre>
	 * &lt;interceptor-ref name=&quot;security&quot;&gt;
	 *     &lt;param name=&quot;role&quot;&gt;<em>required role</em>&lt;/param&gt;
	 * &lt;/interceptor-ref&gt;
	 * </pre>
	 * 
	 * @param role required role.
	 */
	public void setRole (String role)
	{
		this.role = role;
	}

	/**
	 * Interceptor destory.
	 */
	public void destroy ()
	{
	}

	/**
	 * Interceptor init.
	 */
	public void init ()
	{
	}

	/**
	 * Intercepts the action invocation.
	 * 
	 * @param invocation action invocation.
	 * @return string action result.
	 * @throws Exception if action failed or a security exception occurred.
	 */
	public String intercept (ActionInvocation invocation) throws Exception
	{
		SecurityContext securityContext = SecurityContext.getContext();
		
		checkRole(securityContext);
		
		injectSecurityContext(invocation.getAction(), securityContext);
		
		return invocation.invoke();
	}
	
	/**
	 * Checks if the user is in required role.
	 * 
	 * @param securityContext security context.
	 * @throws NotLoggedInException if role is supplied and user is not logged in.
	 * @throws NotInRoleException if logged in user is not in the required role.
	 */
	private void checkRole (SecurityContext securityContext) throws NotLoggedInException, NotInRoleException
	{
		if (role == null)
			return;

		if (!securityContext.isLoggedIn())
			throw new NotLoggedInException();
		
		if (!securityContext.isInRole(role))
			throw new NotInRoleException();
	}
	
	/**
	 * Injects the given {@link SecurityContext} into the given action
	 * if action is {@link SecurityAware}.
	 * 
	 * @param action action object.
	 * @param securityContext security context.
	 */
	private static void injectSecurityContext (Object action, SecurityContext securityContext)
	{
		if (!(action instanceof SecurityAware))
			return;
		
		((SecurityAware) action).setSecurityContext(securityContext);
	}
}
